/**
 *    Copyright (C) 2007-2009 DIVIS GmbH (http://www.divis-hagen.de/)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 3 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General
 *    Public License along with this library; if not, write to the
 *    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *    Boston, MA 02110-1301 USA
 */

package de.divis_hagen.json;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 *
 * @author Aike J Sommer
 */
public class Helpers {

    public static boolean isSupported(Class c) {
        return c.isAnnotationPresent(XmlRootElement.class)
                || c.isAnnotationPresent(XmlType.class)
                || c.isPrimitive()
                || String.class.isAssignableFrom(c)
                || Long.class.isAssignableFrom(c)
                || Integer.class.isAssignableFrom(c)
                || Short.class.isAssignableFrom(c)
                || Byte.class.isAssignableFrom(c)
                || Character.class.isAssignableFrom(c)
                || Boolean.class.isAssignableFrom(c)
                || Float.class.isAssignableFrom(c)
                || Double.class.isAssignableFrom(c)
                || c.isEnum();
    }

    public static Class getRaw(Type type) {
        if (type instanceof Class) {
            return (Class) type;
        } else if (type instanceof ParameterizedType) {
            Type raw = ((ParameterizedType) type).getRawType();
            return (raw instanceof Class) ? (Class) raw : null;
        }

        return null;
    }

    public static Type[] getTypes(Type from, Class as) {
        Class fromClass = getRaw(from);
        if (fromClass == null) {
            return null;
        }

        Type[] fromTypes = resolveTypes(from, new TypeVariable[0], new Type[0]);

        return getTypes(fromClass, fromTypes, as);
    }

    private static Type[] resolveTypes(Type type, TypeVariable[] vars, Type[] types) {
        Type[] fromTypes;
        if (type instanceof ParameterizedType) {
            ParameterizedType parType = (ParameterizedType) type;
            fromTypes = new Type[parType.getActualTypeArguments().length];

            for (int i = 0; i < parType.getActualTypeArguments().length; i++) {
                Type arg = parType.getActualTypeArguments()[i];
                if ((arg instanceof Class) || (arg instanceof ParameterizedType)) {
                    fromTypes[i] = arg;
                } else if (arg instanceof TypeVariable) {
                    String name = ((TypeVariable) arg).getName();
                    Type found = null;
                    for (int j = 0; j < vars.length; j++) {
                        TypeVariable var = vars[j];
                        if (var.getName().equals(name)) {
                            found = types[j];
                            break;
                        }
                    }
                    if (found == null) {
                        throw new IllegalArgumentException();
                    }

                    fromTypes[i] = found;
                } else {
                    throw new IllegalArgumentException();
                }
            }
        } else {
            fromTypes = new Type[0];
        }

        return fromTypes;
    }

    private static Type[] getTypes(Class fromClass, Type[] fromTypes, Class as) {
//        Class fromClass = (Class) from;
        if (as.equals(fromClass)) {
            return fromTypes;
        } else if (! as.isAssignableFrom(fromClass)) {
            return null;
        }

        for (Type type : fromClass.getGenericInterfaces()) {
            Type[] result = getTypes(getRaw(type), resolveTypes(type, fromClass.getTypeParameters(), fromTypes), as);
            if (result != null) {
                return result;
            }
        }
        if (fromClass.getGenericSuperclass() != null) {
            Type type = fromClass.getGenericSuperclass();
            Type[] result = getTypes(getRaw(type), resolveTypes(type, fromClass.getTypeParameters(), fromTypes), as);
            if (result != null) {
                return result;
            }
        }

        return null;
    }

    public static String valueProperty(TypeMeta.Complex<Object> meta) {
        if (meta.getProperties().size() == 1 && meta.getProperties().iterator().next().getFlags().isValue()) {
            TypeMeta.Property<Object, Object, Object> property = meta.getProperties().iterator().next();
            return property.getName();
        }
        return null;
    }

}
